/*
Acess OS
Virtual File System Version 1.5
- New File Descriptor Layout
*/
#include <acess.h>
#include <vfs.h>
#include <vfs_int.h>

#define VFS_DEBUG	0
#define DEBUG	(VFS_DEBUG | ACESS_DEBUG)
#define	DEBUG_WARN	1
#define VERBOSE	0

#if VERBOSE
# define	NOTE(v...)	LogF(v)
#else
# define	NOTE(v...)
#endif

//CONSTANTS
#define	MAX_MOUNTS	16
#define FH_K_FLAG	0x8000

#define	VFS_TRUEPATHEX_USER		0x1
#define	VFS_TRUEPATHEX_NODEONLY	0x2

//GLOBAL VARIABLES
tVFS_Driver		*gVFS_Drivers;
t_fs_initDev	vfsDrivers[512];
t_vfs_mount		mounts[MAX_MOUNTS];
tFileHandle		*gpFirstHandle = NULL;
tFileHandle		*gpKernelHandles = NULL;
vfs_node		NULLNode;
unsigned int	mountCount = 0;
unsigned int	giVfsLastFile = 0;
unsigned int	giVfsLastKFile = 0;

//IMPORTS
extern void	devfs_install();	// Device File System - Integral to system
extern int	devfs_ioctl(vfs_node *node, int id, void *data);

extern void rootfs_install();	// Root File System
extern void rom_install();		// Basic ROM Ramdisk Driver

// ==== PROTOTYPES ====
 int	vfs_checkPerms(vfs_node *node, int req);
tFileHandle	*vfs_getFileHandle(int id);
 int	vfs_mount(char *Device, char *MountPoint, char *Options, tVFS_Driver *Driver);
char	*VFS_GetTruePathEx(char *path, vfs_node **endNode, int flags, int *MountId);
void	VFS_DumpNode(vfs_node *node);

//CODE
/*!	\fn void vfs_install(void)
 *	\brief Install and setup Virtual Filesystem layer
 *
 *	vfs_install is the 'entry point' of the VFS system, it loads
 *	all the file systems and then initalizes the core filesystems,
 *	RootFS and DevFS.
 */
void vfs_install(void)
{
	int i;
	
	gpFirstHandle = (void*)0xE4000000;
	gpKernelHandles = (void *)0xC4000000;
	
	//Initalise Driver List
	for(i=0;i<512;i++)		vfsDrivers[i] = NULL;
	
	// Load Core FS Drivers
	rootfs_install();
	devfs_install();
	
	// Create Filesystem
	// TODO: Allow dynamic setup in `config.c`
	VFS_MountName("root", "/", "", "root");
	mounts[0].MountPointLen = 0;	//Hack (Used to ignore the first '/')
	vfs_mknod("/Devices", VFS_FFLAG_DIRECTORY);
	vfs_mknod("/Mount", VFS_FFLAG_DIRECTORY);
	
	VFS_MountName("devfs", "/Devices", "", "devfs");
	
	// Initialise NULLNode
	NULLNode.name = NULL;	NULLNode.nameLength = 0;
	NULLNode.impl = -1;	NULLNode.inode = -1;
	NULLNode.mode = 0;	NULLNode.uid = 0;	NULLNode.gid = 0;
	NULLNode.read = NULL;	NULLNode.write = NULL;	NULLNode.close = NULL;
	NULLNode.readdir = NULL;	NULLNode.finddir = NULL;	NULLNode.relink = NULL;
}

/*!	\fn int vfs_addfs(Uint16 ident, t_fs_initDev initDev)
 *	\brief Add a filesystem to be handled
 *	\param ident Filesystem identifier - read from MBR
 *	\param initDev Function pointer to filesystem initializer
 *
 *	This function is called by filesystem drivers to make the vfs layer aware
 *	of the filesystems it can read and the addresses of its functions.
 */
int vfs_addfs(Uint16 ident, t_fs_initDev initDev)
{
	if(vfsDrivers[ident] != NULL) {
		return 0;
	}
	LogF("[VFS ] Filesystem 0x%x Added\n", ident);
	vfsDrivers[ident] = initDev;
	return 1;
}

/**
 * \fn int VFS_AddDriver(tVFS_Driver *Info)
 * \brief Add a Filesystem Driver
 * \param Info	FS Driver Information
 */
int VFS_AddDriver(tVFS_Driver *Info)
{
	Info->Next = gVFS_Drivers;
	gVFS_Drivers = Info;
	return 0;
}

/*!	\fn int vfs_open(char *path, int flags)
 *	\brief Open a file for reading/writing
 *	\param path	String containing path to open
 *	\param flags	Flags
 */
int vfs_open(char *path, int flags) 
{
	 int	mnt, i;
	 int	intHandle = 0;
	char	*tmpName;
	vfs_node	*fileNode;
	vfs_node	*tmpNode;
	tFileHandle	*handle;
	tFileHandle	*hs;
	 int	last;
	
	if(path == NULL)
		return -1;
	
	#if DEBUG
		LogF("vfs_open: (path='%s', flags=%i)\n", path, flags);
	#endif
	 
	// --- Get Handle
	// Get Pointers
	if(flags & VFS_OPENFLAG_USER) {
		hs = gpFirstHandle;
		last = giVfsLastFile;
	} else {
		hs = gpKernelHandles;
		last = giVfsLastKFile;
	}
	
	// Check for free entry
	for( intHandle = 0; intHandle < last; intHandle++ ) {
		if( hs[ intHandle ].node == NULL)
			break;
	}
	
	// If list is full
	if( intHandle == last )
	{
		Uint	addr = ( (Uint)(hs + last) + 0xFFF) & 0xFFFFF000;
		intHandle = last;
		last = (addr+0x1000-(Uint)hs) / sizeof( *hs );
		if( (addr-(Uint)hs) > 0x80000000 && flags & VFS_OPENFLAG_USER) {
			WarningEx("VFS", "Max file limit of %i files reached in process #%i\n", last, proc_pid);
			return -1;
		}
		if( (addr-(Uint)hs) > 0x40000000 && !(flags & VFS_OPENFLAG_USER) ) {
			WarningEx("VFS", "Max file limit of %i files reached in kernel\n", last);
			return -1;
		}
		mm_alloc( addr );
		memsetda( (void*)addr, 0, 1024 );
		if(flags & VFS_OPENFLAG_USER)
			giVfsLastFile = last;
		else
			giVfsLastKFile = last;
	}
	
	// Get Handle Structure
	handle = &hs[ intHandle ];
	
	// Add Kernel Flag
	if( !(flags & VFS_OPENFLAG_USER) )
		intHandle |= FH_K_FLAG;
	// --- END
	
	// --- Pass to Get True Path to parse path
	i = (flags & VFS_OPENFLAG_USER) ? VFS_TRUEPATHEX_USER : 0;
	i |= VFS_TRUEPATHEX_NODEONLY;
	VFS_GetTruePathEx( path, &fileNode, i, &mnt );
	
	if(!fileNode)	return -1;	// Check if the file exists
	
	// --- Parse last symlink
	if( !(flags & VFS_OPENFLAG_NOLINK) && (fileNode->flags & VFS_FFLAG_SYMLINK) )
	{
		tmpName = malloc(fileNode->length+1);
		fileNode->read(fileNode, 0, fileNode->length, tmpName);
		tmpName[ fileNode->length ] = '\0';	// NULL Terminator
		tmpNode = fileNode;
		VFS_GetTruePathEx( tmpName, &fileNode, i, &mnt );
		if(tmpNode->close)	tmpNode->close(tmpNode);
		free(tmpName);
	}
	
	// --- Check Permissions
	if(flags & VFS_OPENFLAG_USER)	// Root is always allowed
	{
		i = 0;
		if(flags & VFS_OPENFLAG_READ)	i |= 4;
		if(flags & VFS_OPENFLAG_WRITE)	i |= 2;
		if(flags & VFS_OPENFLAG_EXEC)	i |= 1;
		if(vfs_checkPerms(fileNode, i) == 0)
			return -1;
	}
	
	// Set handle info
	handle->Mount = &mounts[mnt];
	handle->mode = flags & VFS_OPENFLAG_MASK;
	handle->node = fileNode;
	handle->pos = 0;
	
	#if DEBUG
		LogF("vfs_open: `%s' opened as 0x%x (fileNode=%p)\n", path, intHandle, fileNode);
	#endif
	
	return intHandle;
}

/*!	\fn int vfs_opendir(char *path)
 *	\brief Open a directory for reading
 *	\param path String containing path to open as dir
 *
 *	This is just a dummy function that passes control to
 *	vfs_open as they are functionally identical
 */
int vfs_opendir(char *path) 
{
	return vfs_open(path, VFS_OPENFLAG_EXEC);
}

/**
 \fn char *VFS_GetTruePath(char *path)
 \brief Evaluates a path, resolving all symbolic links and returns the final path
 \param path	String - Logical path
*/
char *VFS_GetTruePath(char *path)
{
	return VFS_GetTruePathEx(path, NULL, 0, NULL);
}

/**
 \fn char *VFS_GetTruePathEx(char *path, vfs_node **endNode, int flags, int *MountId)
 \brief Evaluates a path, resolving all symbolic links and returns the final path
 \param path	String - Logical path
 \param endNode	Node of the file that \a path points to
 \param flags	Traversal flags (Node Only, User)
 \param MountId	Where to save the ID of mountpoint that \a path is on
*/
char *VFS_GetTruePathEx(char *path, vfs_node **endNode, int flags, int *MountId)
{
	 int	mnt, longestMnt = -1;
	 int	retLen = 0;
	 int	slashPos, start;
	 int	tmpCheck;
	char	*ret = NULL;
	void	*tmpPtr;
	vfs_node	*node, *nextNode;
	
	//LogF("VFS_GetTruePathEx: (path='%s', endNode=0x%x, flags=%x)\n", path, endNode, flags);
	
	// Get Mount Point
	for(mnt=0;mnt<mountCount;mnt++)
	{
		tmpCheck = strcmp(path, mounts[mnt].MountPoint);
		// Root of Filesystem
		if(tmpCheck == '\0')
		{
			if(mounts[mnt].rootNode == NULL)
			{
				warning("VFS_GetTruePath - %s is mounted incorrectly\n", mounts[mnt].MountPoint);
				if(endNode)	*endNode = NULL;
				return NULL;
			}
			
			// NOTE: No permissions check nessasary because we are not traversing this.
			
			if(MountId)	*MountId = mnt;
			if(endNode)	*endNode = mounts[mnt].rootNode;
			if( !(flags & VFS_TRUEPATHEX_NODEONLY) ) {
				ret = malloc( strlen(path) + 1);
				strcpy(ret, path);
			}
			return ret;
		}
		
		// Below the root - Needs to be checked so that a mount on a mount is valid
		if(tmpCheck == '/') {
			if(mounts[mnt].MountPointLen >  mounts[longestMnt].MountPointLen)
				longestMnt = mnt;
		}
	}
	
	// Check if a non root mount was found
	if(longestMnt == -1)
		mnt = 0;
	else
		mnt = longestMnt;
		
	// Return Mount ID (May be changed by symlinks later)
	if(MountId)	*MountId = mnt;
	
	// Create start of string
	if( !(flags & VFS_TRUEPATHEX_NODEONLY) )
	{
		retLen = mounts[mnt].MountPointLen;
		ret = (char *) malloc(retLen + 1);
		memcpy(ret, mounts[mnt].MountPoint, retLen);
		ret[retLen] = '\0';
	}
	
	// Init scan variables
	start = slashPos = mounts[mnt].MountPointLen + 1;
	tmpCheck = 0;
	
	// Scan Filesystem Hierachy
	node = mounts[mnt].rootNode;
	while(node)
	{
		
		// Check for Execute Permissions (traverse)
		if(flags & VFS_TRUEPATHEX_USER)
		{
			if(vfs_checkPerms(node, 1) == 0) {
				if(endNode)	*endNode = NULL;
				return NULL;
			}
		}
		
		// Parse Symbolic Links
		if(node->flags & VFS_FFLAG_SYMLINK)
		{
			#if DEBUG >= 2
			LogF(" VFS_GetTruePathEx: Symlink '%s'\n", ret);
			#endif
			// Free return if it is in use
			if( !(flags & VFS_TRUEPATHEX_NODEONLY) )
				free(ret);
			
			// Check if the file has a read call
			if(!node->read) {
				WarningEx("VFS", "Symlink node '%s' has no read method\n", node->name);
				if(node->close)	node->close(node);
				if(endNode)	*endNode = NULL;
				return NULL;
			}
			
			// Allocate space for link address
			tmpPtr = ret = malloc( node->length + 1);
			
			// Get Link Address
			node->read(node, 0, node->length, ret);
			ret[node->length] = '\0';
			// Close link handle
			//LogF(" VFS_GetTruePathEx: Closing node '%s' 0x%x - Symlink\n", node->name, node->inode);
			if(node->close)	node->close(node);
			
			// Parse Link (Getting endpoint node)
			ret = VFS_GetTruePathEx( ret, &node, flags, MountId );
			free(tmpPtr);
			
			if(!ret)	return NULL;	// Catch broken links
			
			if( !(flags & VFS_TRUEPATHEX_NODEONLY) )
				retLen = strlen( ret );
			continue;
		}
		
		
		// Parse Directories
		if(node->flags & VFS_FFLAG_DIRECTORY)
		{
			while(path[slashPos] && path[slashPos] != '/')	slashPos++;
			if(path[slashPos] == 0)	tmpCheck = 1;	// Mark at end of string
			path[slashPos] = '\0';
			
			nextNode = node->finddir(node, path+start);
			
			if(!tmpCheck)	path[slashPos] = '/';
			
			
			slashPos ++;
			start = slashPos;
			//LogF(" VFS_GetTruePathEx: Closing node '%s' 0x%x - Directory\n", node->name, node->inode);
			if(node->close)	node->close(node);
			node = nextNode;
			
			// NULL Check
			if(node == NULL) {
				free(ret);
				if(endNode)	*endNode = NULL;
				return NULL;
			}
			
			if( !(flags & VFS_TRUEPATHEX_NODEONLY) )
			{
				// Append to string
				tmpPtr = realloc(ret, retLen+2+node->nameLength);
				if(tmpPtr == NULL) {
					free(ret);
					if(endNode)	*endNode = NULL;
					if(node->close)	node->close(node);
					return NULL;
				}
				ret = tmpPtr;
				ret[retLen] = '/';
				memcpy(ret+retLen+1, node->name, node->nameLength);
				retLen += 1+node->nameLength;
				#if DEBUG >= 2
				LogF(" VFS_GetTruePathEx: ret = '%s'\n", ret);
				#endif
			}
			
			if(tmpCheck) {
				path[slashPos-1] = '\0';	// Repair String
				// Cap off string
				if( !(flags & VFS_TRUEPATHEX_NODEONLY) )
					ret[retLen] = '\0';
				break;
			}
		}
		else
		{
			// Cap off string
			if( !(flags & VFS_TRUEPATHEX_NODEONLY) )
				ret[retLen] = '\0';
			break;
		}
	}
	
	if(endNode)
		*endNode = node;
	else if(node && node->close) {
		//LogF(" VFS_GetTruePathEx: Closing node '%s' 0x%x - Final (endNode == NULL)\n", node->name, node->inode);
		node->close( node );
	}
	
	//LogF("VFS_GetTruePathEx: RETURN '%s', node=0x%x\n", ret, node);
	return ret;
}

/*!	\fn int vfs_readdir(int fh, char *name)
 *	\brief Reads the next file from a directory
 *	\param fh	Handle to directory returned by vfs_opendir
 *	\param name	Pointer to string where name of file will be put
 *
 *	This function is calls the relevant function of the filestsyem driver
 *	to read the directory
 */
int vfs_readdir(int fh, char *name)
{
	vfs_node	*dirNode;
	vfs_node	*childNode;
	tFileHandle	*handle;
	
	#if DEBUG >= 2
		LogF("vfs_readdir: (fh=0x%x, name=*0x%x)\n", fh, name);
	#endif
	
	handle = vfs_getFileHandle( fh );
	
	// Check validity of handle
	if(handle == NULL)
		return 0;
	
	// Ignore Null Destination
	if(name == NULL)
		return -1;
	
	// Get Node
	dirNode = handle->node;
	
	//VFS_DumpNode(dirNode);
	
	// Check if the node is a directory
	if(!(dirNode->flags & VFS_FFLAG_DIRECTORY) || dirNode->readdir == NULL)
		return -2;
	if( !(handle->mode & VFS_OPENFLAG_EXEC) )
		return -3;
	
	// Read entry skipping empty/ignored entries
	for(;;) {
		childNode = dirNode->readdir(dirNode, handle->pos);
		if(childNode == NULL)	break;
		if((int)childNode < 0) {
			handle->pos++;
			if(childNode != &NULLNode)	break;
			
		}
		else
			handle->pos += (int)childNode;
	}
	
	//End of list
	if(childNode == NULL)
		return 0;
	
	if(childNode->name == NULL)
		return 0;
	
	//Copy to name
	memcpy(name, childNode->name, childNode->nameLength+1);
	
	// Close Node
	if(childNode->close)
		childNode->close(childNode);
	
	return 1;
}

/*!	\fn int vfs_read(int fh, int length, void *buffer)
 *	\brief Read data from a file
 *	\param fh	Handle returned by vfs_open
 *	\param length	Byte ammount of data to read
 *	\param buffer	Place to put data read
 */
int vfs_read(int fh, int length, void *buffer)
{
	vfs_node	*node;
	 int	ret;
	tFileHandle	*handle = vfs_getFileHandle( fh );
	
	if(handle == NULL)
		return -1;
	
	#if DEBUG >= 2
		LogF("vfs_read: (fh=0x%x,length=%i,buffer=*0x%x)\n", fh, length, (Uint)buffer);
		LogF(" vfs_read: mount = %i\n", handle->mount);
	#endif
	
	if(!(handle->mode & VFS_OPENFLAG_READ))	// Check Mode
	{
		WarningEx("VFS", "Non-Allowed Read fh=0x%x, (Perms: 0%o)\n", fh, handle->mode);
		return 0;
	}
	
	if(length <= 0)
	{
		#if DEBUG >= 3
			LogF(" vfs_read: 0 bytes to read, quick returning\n");
		#endif
		return 0;
	}
	
	node = handle->node;
	
	if(!node) {
		WarningEx("VFS", "Node of handle 0x%x is NULL, how did this happen?\n", fh);
		return 0;
	}
	
	#if DEBUG >= 3
		LogF(" vfs_read: node = (vfs_node*)(0x%x)\n", (Uint)node);
	#endif
	
	if(node->length > 0 && handle->pos+length > node->length) {
		if(handle->pos > node->length)
			return 0;
		length = node->length - handle->pos;
	}
	
	#if DEBUG >= 3
		LogF(" vfs_read: Read from %i to %i\n", handle->pos, handle->pos+length);
	#endif
	
	if(node->read == NULL)
	{
		#if DEBUG_WARN
			WarningEx("VFS", "No Read method for node (fp=0x%x,handle=%p,node->name='%s')\n", fh,handle,node->name);
		#endif
		return -1;
	}
	 
	ret = node->read(node, handle->pos, length, buffer);
	handle->pos += length;
	
	return length;	// Return number of bytes read
}

/**
 * \fn int VFS_ReadAt(int fh, Uint64 Offset, Uint64 Length, void *Buffer)
 * \brief Read bytes from an offset in a file
 * \param fh	File Handle
 * \param Offset	Byte offset in file
 * \param Length	Number of bytes to read
 * \param Buffer	Destination for the data
 */
int VFS_ReadAt(int fh, Uint64 Offset, int Length, void *Buffer)
{
	vfs_node	*node;
	 int	ret;
	tFileHandle	*handle = vfs_getFileHandle( fh );
	
	// Check if the handle is valid
	if(handle == NULL)	return -1;
	if(handle->node == NULL)	return -1;
	
	if(!(handle->mode & VFS_OPENFLAG_READ)) {	// Check Mode
		WarningEx("VFS", "Non-Allowed Read fh=0x%x, (Perms: 0%o)\n", fh, handle->mode);
		return 0;
	}
	
	// Fast quit check
	if(Length <= 0)	return 0;
	
	// Get Handle's node
	node = handle->node;
	
	// Check for an overflow
	if(node->length > 0 && Offset+Length > node->length) {
		if(Offset > node->length)
			return 0;
		Length = node->length - Offset;
	}
	
	if(node->read == NULL)
	{
		#if DEBUG_WARN
			WarningEx("VFS", "No Read method for node (fp=0x%x,node->name='%s')\n", handle, node->name);
		#endif
		return -1;
	}
	 
	ret = node->read(node, Offset, Length, Buffer);
	
	return Length;	// Return number of bytes read
}

/*!	\fn int vfs_write(int fh, int length, void *buffer)
 *	\brief Read data from a file
 *	\param fh	Handle returned by vfs_open
 *	\param length	Byte ammount of data to write
 *	\param buffer	Place to put data write
 */
int vfs_write(int fh, int length, void *buffer)
{
	vfs_node	*node;
	int ret;
	tFileHandle	*handle = vfs_getFileHandle( fh );
	
	#if DEBUG >= 2
		LogF("vfs_write: (fh=0x%x, length=%i)\n", fh, length);
	#endif
	
	if(handle == NULL)	return -1;
	if(handle->node == NULL)	return -1;
	
	if(length == 0) {
		return 0;
	}
	
	if( !(handle->mode & VFS_OPENFLAG_WRITE))	// Check Mode
		return 0;
	
	node = handle->node;
	
	if(node->write == NULL){
		#if DEBUG_WARN
		WarningEx("VFS", "No Write Function. fh=0x%x, name='%s'\n", fh, node->name);
		#endif
		return -1;
	}
	ret = node->write(node, handle->pos, length, buffer);
	handle->pos += length;
	return ret;
}

/**
 * \fn int VFS_WriteAt(int fh, Uint64 Offset, Uint64 Length, void *Buffer)
 * \brief Write bytes from an offset in a file
 * \param fh	File Handle
 * \param Offset	Byte offset in file
 * \param Length	Number of bytes to write
 * \param Buffer	Source for the data
 */
int VFS_WriteAt(int fh, Uint64 Offset, int Length, void *Buffer)
{
	vfs_node	*node;
	 int	ret;
	tFileHandle	*handle = vfs_getFileHandle( fh );
	
	// Check if the handle is valid
	if(handle == NULL)	return -1;
	if(handle->node == NULL)	return -1;
	
	// Check Open Mode
	if(!(handle->mode & VFS_OPENFLAG_WRITE)) {
		WarningEx("VFS", "Non-Allowed Write fh=0x%x, (Perms: 0%o)\n", fh, handle->mode);
		return 0;
	}
	
	// Fast quit check
	if(Length <= 0)	return 0;
	
	// Get Handle's node
	node = handle->node;
	
	if(node->write == NULL)
	{
		#if DEBUG_WARN
			WarningEx("VFS", "No Read method for node (fp=0x%x,node->name='%s')\n", handle, node->name);
		#endif
		return -1;
	}
	 
	ret = node->write(node, Offset, Length, Buffer);
	
	return Length;	// Return number of bytes read
}

/*!	\fn int vfs_seek(int fh, int distance, int flag)
 *	\brief Alter file position
 *	\param fh	Handle to seek
 *	\param distance	Ammount to seek by
 *	\param flag	How to seek (0: Relative, 1:Absolute, -1:End)
 */
void vfs_seek(int fh, int distance, int flag)
{
	tFileHandle	*h = vfs_getFileHandle( fh );
	if(h == NULL)	return;
	switch(flag)
	{
	case 0:
		h->pos += distance;
		return;
	case 1:
		h->pos = distance;
		return;
	case -1:
		h->pos = h->node->length - distance;
		return;
	}
}

/*!	\fn int vfs_tell(int fh)
 *	\brief Returns the file position
 *	\param fh	Handle
 */
int vfs_tell(int fh)
{
	tFileHandle	*h = vfs_getFileHandle( fh );
	if(h == NULL)	return -1;
	return h->pos;
}

/*!	\fn int vfs_close(int fh)
 *	\brief Closes a file
 *	\param fh	Handle to close
 */
void vfs_close(int fh)
{
	tFileHandle	*h = vfs_getFileHandle( fh );
	#if DEBUG
		LogF("vfs_close: (fileHandle=0x%x)\n", fh);
	#endif
	
	// Error Check
	if(h == NULL)	return;
	
	// Close node
	if( h->node && h->node->close )
		h->node->close(h->node);
	
	// Unset Handle
	h->Mount = NULL;
	h->node = NULL;
}

/*!	\fn int vfs_stat(int fh, t_fstat *info)
 *	\brief Returns File information
 *	\param fh	Integer - File Pointer
 *	\param info	Pointer - Pointer to t_fstat structure
 */
int vfs_stat(int fh, t_fstat *info)
{
	vfs_node	*node;
	tFileHandle	*h = vfs_getFileHandle( fh );
	
	//  Error check
	if( h == NULL )	return 0;
	
	// Get Node pointer
	node = h->node;
	
	info->st_dev = (Uint)&h->Mount-(Uint)mounts;
	info->st_ino = node->inode;
	info->st_mode = node->mode;
	if(node->flags & VFS_FFLAG_DIRECTORY)
		info->st_mode |= S_IFDIR;
	if(node->flags & VFS_FFLAG_SYMLINK)
		info->st_mode |= S_IFLNK;
	info->st_nlink = 1;
	info->st_uid = node->uid;
	info->st_gid = node->gid;
	info->st_rdev = 0;
	info->st_size = node->length;
	info->st_atime = node->atime;
	info->st_mtime = node->mtime;
	info->st_ctime = node->ctime;
	
	return 1;
}

/**
 * \fn int VFS_MountName(char *Device, char *MountPoint, char *Options, char *Name)
 * \brief Mount a device given a filesystem type name
 */
int VFS_MountName(char *Device, char *MountPoint, char *Options, char *Name)
{
	tVFS_Driver	*drv;
	
	for(drv = gVFS_Drivers; drv; drv = drv->Next)
	{
		if(strcmp(drv->Name, Name) == 0)
			return vfs_mount(Device, MountPoint, Options, drv);
	}
	return 0;
}

/**
 * \fn int VFS_MountMBR(char *Device, char *MountPoint, char *Options, Uint8 Id)
 * \brief Mount a device given a MBR Type
 */
int VFS_MountMBR(char *Device, char *MountPoint, char *Options, Uint8 Id)
{
	tVFS_Driver	*drv;
	 int	i;
	
	for(drv = gVFS_Drivers; drv; drv = drv->Next)
	{
		if( !drv->MBRIds )	continue;
		for(i=0;drv->MBRIds[i];i++)
			if(drv->MBRIds[i] == Id)
				return vfs_mount(Device, MountPoint, Options, drv);
	}
	return 0;
}

/**
 * \fn int VFS_MountGPT(char *Device, char *MountPoint, char *Options, Uint64 Id)
 * \brief Mount a device given a GPT Type
 */
int VFS_MountGPT(char *Device, char *MountPoint, char *Options, Uint64 Id)
{
	tVFS_Driver	*drv;
	 int	i;
	
	for(drv = gVFS_Drivers; drv; drv = drv->Next)
	{
		if( !drv->GPTIds )	continue;
		for(i=0;drv->GPTIds[i];i++)
			if(drv->GPTIds[i] == Id)
				return vfs_mount(Device, MountPoint, Options, drv);
	}
	return 0;
}

/*!	\fn int vfs_mount(char *Device, char *MountPoint, char *Options, tVFS_Driver *Driver)
 *	\brief Mounts a device so it can be accessed
 *	\param device		String defining the device to be mounted
 *	\param mountPoint	The place to mount the object
 *  \param Options	String describing options for filesystem
 *	\param Driver	Specifies what file system should be used
 *
 *	This determines what type of device is going to be mounted and
 *	then binds the device/file to the mount point.
 */
int vfs_mount(char *Device, char *MountPoint, char *Options, tVFS_Driver *Driver)
{
	t_vfs_mount	*mnt = &mounts[mountCount];
	
	#if DEBUG
		LogF("vfs_mount: (Device='%s', MountPoint='%s', Options='%s', Driver=%p)\n",
			Device, MountPoint, Options, Driver);
	#endif
	
	if(!Driver)	return 0;
	
	#if DEBUG
		LogF(" vfs_mount: %s => %s (FS '%s')\n", Device, MountPoint, Driver->Name);
	#endif
	
	if(mountCount == MAX_MOUNTS) {
		WarningEx("VFS", "Out of Avaliable Mounts\n");
		return 0;
	}
	
	//Get Device Type
	if(Device[0] != '/')
		mnt->type = SYS_VIRTUAL;
	else
	{
		if(Device[1] == '/')
			mnt->type = SYS_NETWORK;
		else
			mnt->type = SYS_NORMAL;
	}
	
	#if DEBUG
		LogF(" vfs_mount: mnt->type = %i\n", mnt->type);
	#endif
	
	//Switch
	switch(mnt->type)
	{
	case SYS_NORMAL:
	case SYS_VIRTUAL:
		mnt->Driver = Driver;
		mnt->rootNode = Driver->InitDevice(Device, NULL);
		break;
	}
	if(mnt->rootNode == NULL) {
		WarningEx("VFS", "Error Occured while mounting `%s'", Device);
		return 0;
	}
	
	mnt->MountPointLen = strlen( MountPoint );
	mnt->MountPoint = (char *) malloc( mnt->MountPointLen + 1 );
	strcpy(mnt->MountPoint, MountPoint);
	
	mnt->Device = (char *) malloc( strlen( Device ) + 1 );
	strcpy(mnt->Device, Device);
	
	
	NOTE("[VFS ] Mounted '%s' to '%s'\n", Device, MountPoint);
	
	mountCount++;
	return 1;
}

/*! \fn int vfs_mknod(char *path, int flags);
 *  \brief Creates a new filesystem node
 */
int vfs_mknod(char *path, int flags)
{
	 int	pos, pathLen;
	vfs_node	*ret = NULL;
	char	*name;
	
	// Get Length of path
	pathLen = strlen(path);
	
	// Split Path
	for(pos=pathLen;pos--;) {
		if(path[pos] == '/')	// Check for slash 
		{
			path[pos] = '\0';
			break;
		}
	}
	pos++;
	name = path + pos;
	
	// Check if on root of filesystem
	if(pos == 1)
	{
		if(mounts[0].rootNode->mknod != NULL) {
			ret = mounts[0].rootNode->mknod(mounts[0].rootNode, name, flags);
		} else {
			WarningEx("VFS", "Read-only filesystem (No mknod method)\n");
		}
	}
	else
	{
		vfs_node	*childNode;
		VFS_GetTruePathEx( path, &childNode, VFS_TRUEPATHEX_NODEONLY, NULL );
		//handle = vfs_getFileHandle( id = vfs_open(path, 0) );
		if(childNode == NULL)
		{
			WarningEx("VFS", "MkNod - No Such directory\n");
			return 0;
		}
		else
		{
			if(childNode->mknod != NULL) {
				ret = childNode->mknod(childNode, name, flags);
			} else {
				WarningEx("VFS", "Read-only filesystem (No mknod method)\n");
			}
			if(childNode->close)	childNode->close(childNode);
		}
	}
	
	path[pos-1] = '/';	// Repair Path
	
	if(ret == NULL)		return 0;
	if(ret->close != NULL)	ret->close(ret);
	return 1;
}

/**
 * \fn int vfs_ioctl(int fh, int id, void *data)
 * \brief Calls IOCtl on a file IF it is a device
 * \note This is a bit of a hack as it directly calls the devfs
 */
int vfs_ioctl(int fh, int id, void *data)
{
	tFileHandle	*h = vfs_getFileHandle( fh );
	if( h == NULL )	return -1;
	if( h->Mount->Driver->IOCtl == NULL )
		return -1;
	return  h->Mount->Driver->IOCtl( h->node, id, data );
}

/**
 * \fn int vfs_symlink(char *name, char *link)
 * \brief Creates a symlink called `name` to `link`
 * \param name	Name of symbolic link
 * \param link	Destination of symbolic link
 */
int vfs_symlink(char *name, char *link)
{
	char	*realLink;
	 int	fp;
	
	//LogF("vfs_symlink: (name='%s', link='%s')\n", name, link);
	
	realLink = VFS_GetTruePath( link );
	
	if(!realLink)	return -1;
	
	if( !vfs_mknod(name, VFS_FFLAG_SYMLINK) )	return -2;	// Make link node
	
	fp = vfs_open(name, VFS_OPENFLAG_WRITE|VFS_OPENFLAG_NOLINK);
	vfs_write(fp, strlen(realLink), realLink);
	vfs_close(fp);
	
	free(realLink);
	
	return 1;
}

/**
 * \fn int vfs_checkPerms(vfs_node *node, int req)
 * \brief Checks the permissions on a file
*/
int vfs_checkPerms(vfs_node *node, int req)
{
	req &= 7;
	if(node == NULL)	return 0;
	
	if(Proc_GetUid() == 0)	// Superuser / SYSTEM
		return 1;
	
	if(node->uid == Proc_GetUid())
	{
		if( ((node->mode>>6) & req) == req)
			return 1;
		return 0;
	}
	if(node->gid == Proc_GetGid())
	{
		if( ((node->mode>>3) & req) == req)
			return 1;
		return 0;
	}
	if( (node->mode & req) == req)
		return 1;
	return 0;
}

/**
 * \fn tFileHandle *vfs_getFileHandle(int id)
 * \brief Gets a file handle from a handle id
*/
tFileHandle	*vfs_getFileHandle(int id)
{
	#define VFS_BASE	0xE8000000
	if(id < 0)	return NULL;
	// Kernel File
	if(id & FH_K_FLAG)
	{
		id &= ~FH_K_FLAG;
		if(id > giVfsLastKFile)	return NULL;
		return	&gpKernelHandles[id];
	}
	else
	{
		if(id > giVfsLastFile)	return NULL;
		return	&gpFirstHandle[id];
	}
}

/**
 * \fn vfs_node *VFS_GetNodeAndClose(int fp)
 * \brief Get the node of the file associated with fp and close fp
 */
vfs_node *VFS_GetNodeAndClose(int fp)
{
	tFileHandle	*handle = vfs_getFileHandle(fp);
	vfs_node	*ret;
	
	if(!handle)	return NULL;
	
	ret = handle->node;
	
	handle->Mount = NULL;
	handle->node = NULL;
	
	return ret;
}

/**
 * \fn void VFS_DumpNode(vfs_node *node)
 * \brief Dump the contents of a VFS node
 */
void VFS_DumpNode(vfs_node *node)
{
	LogF("VFS Node: 0x%x\n", node);
	if(!node)	return;
	LogF(" ->name       = (%i) '%s'\n",	node->nameLength, node->name);
	LogF(" ->inode      = 0x%llx\n",	node->inode);
	LogF(" ->impl       = 0x%x\n",	node->impl);
	LogF(" ->ImplPtr    = %p\n",	node->impl);
	LogF(" ->length     = %i\n",	node->length);
	LogF(" ->gid:uid    = %i:%i\n",	node->gid, node->uid);
	LogF(" ->mode       = 0%o\n",	node->mode);
	LogF(" ->flags      = 0b%b\n",	node->flags);
	LogF(" ->atime      = %i\n",	node->atime);
	LogF(" ->mtime      = %i\n",	node->mtime);
	LogF(" ->ctime      = %i\n",	node->ctime);
	
	LogF(" ->read       = 0x%x\n",	node->read);
	LogF(" ->write      = 0x%x\n",	node->write);
	LogF(" ->close      = 0x%x\n",	node->close);
	LogF(" ->readdir    = 0x%x\n",	node->readdir);
	LogF(" ->finddir    = 0x%x\n",	node->finddir);
	LogF(" ->mknod      = 0x%x\n",	node->mknod);
	LogF(" ->relink     = 0x%x\n",	node->relink);
}

//====================
//= INODE.C
//====================
/*
AcessOS v0.2
Inode Cache
*/

//Constants
#define	INODE_ARR_STEP	16

//Structures
typedef struct sCacheEntry {
	 int	handle;
	Uint64	inode;
	int		refCount;
	vfs_node	node;
	struct sCacheEntry	*next;
} cache_entry;

//Globals
 int	inode_nextId = 1;
 int	gSpinlock_InodeCache = 0;
cache_entry	*inode_cache = NULL;

//Prototypes
int 	inode_initCache();
void	inode_clearCache(int handle);
vfs_node	*inode_getCache(int handle, Uint64 inode);
vfs_node	*inode_cacheNode(int handle, Uint64 inode, vfs_node *node);
void	inode_uncacheNode(int handle, Uint64 inode);

//Code
int inode_initCache()
{
	#if DEBUG
		LogF("inode_initCache: Returning %i\n", inode_nextId);
	#endif
	return inode_nextId++;
}

/* Deallocates all cache entries associated with a handle
 */
void inode_clearCache(int handle)
{
	cache_entry	*ent = inode_cache;
	cache_entry	*tmpEnt;
	// Traverse list
	while(ent && ent->next)	// Itterate
	{
		// Matching
		if(ent->next->handle == handle)
		{
			// And delete
			ACQ_LOCK(&gSpinlock_InodeCache);
			tmpEnt = ent->next;
			ent->next = tmpEnt->next;
			REL_LOCK(&gSpinlock_InodeCache);
			free(tmpEnt);
		}
		else // Go to next
			ent = ent->next;
	}
	// Handle Currently ignored start of list
	ent = inode_cache;
	if(ent && ent->handle == handle)
	{
		inode_cache = ent->next;
		free(ent);
	}
}

/* Returns a cached node using it's handle and inode number
 */
vfs_node *inode_getCache(int handle, Uint64 inode)
{
	cache_entry	*ent = inode_cache;
	// Traverse list
	while(ent)
	{
		if(ent->handle == handle && ent->inode == inode) {
			ent->refCount ++;	// Reference
			return &ent->node;
		}
		ent = ent->next;
	}
	return NULL;
}

/* Caches a node
 */
vfs_node *inode_cacheNode(int handle, Uint64 inode, vfs_node *node)
{
	cache_entry	*ent = inode_cache;
	vfs_node	*tmpNode;
	
	#if DEBUG
		LogF("inode_cacheNode: (handle=%i, inode=0x%x)\n", handle, (Uint)inode);
	#endif
	
	// Check if node is already cached
	tmpNode = inode_getCache(handle, inode);
	if( tmpNode != NULL )
	{
		warning("Inode already cached.\n");
		return tmpNode;
	}
	
	// Create New node
	ent = (cache_entry *)malloc(sizeof(cache_entry));
	if(ent == NULL) {
		panic("inode_cacheNode - Out of space for cache table (heap exhausted)\n");
		return NULL;
	}
	
	#if DEBUG
		LogF(" inode_cacheNode: Caching Node\n");
	#endif
	
	//Cache Node
	ent->handle = handle;
	ent->inode = inode;
	ent->refCount = 1;
	memcpy(&ent->node, node, sizeof(vfs_node));
	
	// Append to list
	ACQ_LOCK(&gSpinlock_InodeCache);
	ent->next = inode_cache;
	inode_cache = ent;
	REL_LOCK(&gSpinlock_InodeCache);
	
	#if DEBUG
	LogF("inode_cacheNode: RETURN %p\n", &ent->node);
	#endif
	
	// Return cached node
	return &ent->node;
}

/* Remove a node from the cache
 */
void inode_uncacheNode(int handle, Uint64 inode)
{
	cache_entry	*ent = inode_cache;
	cache_entry	*prev = NULL;
	
	#if DEBUG
		LogF("inode_uncacheNode: (handle=%i, inode=0x%x)\n", handle, inode);
	#endif
	
	for( ; ent; prev=ent, ent=ent->next)
	{
		if(ent->handle == handle && ent->inode == inode)
		{
			ent->refCount --;
			if(ent->refCount > 0)	return;	// Check Reference Count
			// Remove from list
			ACQ_LOCK(&gSpinlock_InodeCache);
			if(prev)	prev->next = ent->next;
			else	inode_cache = ent->next;
			REL_LOCK(&gSpinlock_InodeCache);
			free(ent);
			
			#if DEBUG
			LogF("inode_uncacheNode: Found and freed\n");
			#endif
			return;
		}
	}
	//REL_LOCK(gSpinlock_InodeCache);
}


//==============================
//= Exported Function Pointers
//==============================
//EXPORTAS(vfs_addfs, VFS_AddFS);
EXPORT(VFS_AddDriver);
EXPORTAS(vfs_open, VFS_Open);
EXPORTAS(vfs_close, VFS_Close);
EXPORTAS(vfs_read, VFS_Read);
EXPORTAS(vfs_write, VFS_Write);
EXPORTAS(vfs_seek, VFS_Seek);
EXPORTAS(vfs_tell, VFS_Tell);
EXPORTAS(vfs_readdir, VFS_ReadDir);
EXPORTAS(vfs_mount, VFS_Mount);
EXPORTAS(vfs_mknod, VFS_MkNod);
EXPORTAS(vfs_ioctl, VFS_IOCtl);

EXPORTAS(inode_initCache, Inode_InitCache);
EXPORTAS(inode_clearCache, Inode_ClearCache);
EXPORTAS(inode_getCache, Inode_GetCache);
EXPORTAS(inode_cacheNode, Inode_CacheNode);
EXPORTAS(inode_uncacheNode, Inode_UncacheNode);

